package top.infopub.util;


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.net.ftp.FtpClient;
import sun.net.ftp.FtpProtocolException;
import top.infopub.exception.ServiceException;


/**
 * Ftp远程文件处理功能，使用的是sun.net.ftp.FtpClient
 * @author Awoke
 * @version 2018年9月28日
 * @see FtpUtil
 * @since
 */
public final class FtpUtil {

    private static final Logger log = LoggerFactory.getLogger(FtpUtil.class);

    /**
     * 上传到ftp
     * @param fileStream 本地文件流
     * @param serverPath 服务器地址 不含文件名
     * @param fileName 上传到ftp后的文件名
     * @param config ftpUserConfig
     * @return 
     * @see
     */
    public static boolean uploadToFtpServer(InputStream fileStream, String serverPath,
                                            String fileName, FtpUser config) {
        String serverUrl = config.getIp();
        String userName = config.getUserName();
        String passWord = config.getPassword();
        FtpClient ftpClient;
        try {
            // 此为jdk1.7创建ftp连接
            ftpClient = FtpClient.create(serverUrl);
            // 登录到Ftp服务器
            boolean isOpenSuccess = loginToFtpServer(ftpClient, serverUrl, config.getPort(),
                userName, passWord);
            if (isOpenSuccess) {
                // 以下几步顺序不能错
                serverPath = getFullPath(ftpClient, serverPath);
                createDir(ftpClient, serverPath);
                ftpClient.changeDirectory(serverPath);
                ftpClient.putFile(fileName, fileStream);
                close(ftpClient, null, fileStream);// 关闭
            }
        }
        catch (Exception e) {
            log.error("ftp上传文件失败", e);
            return false;
        }
        log.info("ftp上传文件成功 ftp文件目录{}/{}", serverPath, fileName);
        return true;
    }

    /**
     * 上传到服务器
     * @param file 本地文件
     * @param serverPath 服务器地址 不含文件名
     * @param fileName 上传到ftp后的文件名
     * @param config ftpUserConfig
     * @return 
     * @see
     */
    public static boolean uploadToFtpServer(File file, String serverPath, String fileName,
                                            FtpUser config) {
        try {
            return uploadToFtpServer(getLocalFileInputStream(file), serverPath, fileName, config);
        }
        catch (FileNotFoundException e) {
            log.error("文件不存在", e);
        }
        return false;
    }

    /**
     * 将字符串内容以一个文件的形式保存到ftp
     * @param content 字符串内容
     * @param serverPath 服务器地址 不含文件名
     * @param fileName 上传到ftp后的文件名
     * @param config ftpUserConfig
     * @see
     */
    public static void uploadFtp(String content, String serverPath, String fileName, FtpUser config) {
        ByteArrayInputStream contentStream;
        try {
            contentStream = new ByteArrayInputStream(content.getBytes("utf-8"));
        }
        catch (UnsupportedEncodingException e) {
            log.error("", e);
            throw new ServiceException(e);
        }
        FtpUtil.uploadToFtpServer(contentStream, serverPath, fileName, config);
    }

    /**
     * 从ftp服务器下载文件 
     * @param out 本地文件输出流
     * @param serverPathAndFile ftp服务器上的文件 含完整路径
     * @param config ftpUserConfig
     * @see
     */
    public static void downloadFormFtp(OutputStream out, String serverPathAndFile, FtpUser config) {
        String serverUrl = config.getIp();
        String userName = config.getUserName();
        String passWord = config.getPassword();
        String filepath = serverPathAndFile.substring(0, serverPathAndFile.lastIndexOf("/"));
        String filename = serverPathAndFile.substring(serverPathAndFile.lastIndexOf("/") + 1,
            serverPathAndFile.length());
        FtpClient ftpClient = null;
        try {
            // 此为jdk1.7创建ftp连接
            ftpClient = FtpClient.create(serverUrl);
            // 登录
            loginToFtpServer(ftpClient, serverUrl, config.getPort(), userName, passWord);
            filepath = getFullPath(ftpClient, filepath);
            if (isDirExist(ftpClient, filepath)) {
                ftpClient.changeDirectory(filepath);
                InputStream inputStream = getFtpClientInputStream(ftpClient, filename);
                if (inputStream != null) {
                    writeToLocal(inputStream, out);
                }
                if (inputStream != null) {
                    inputStream.close();
                }
                log.info("ftp下载文件成功 ftp文件目录{}", serverPathAndFile);
            }
        }
        catch (Exception e) {
            log.error("ftp下载文件失败", e);
        }
        finally {
            if (ftpClient != null) {
                try {
                    ftpClient.close();
                }
                catch (IOException e) {
                    log.error("", e);
                }
            }
        }
    }
   
    /**
     * 从ftp服务器打包zip文件并下载文件 
     * @param zipName 打包后的zip文件名
     * @param filepath 下载文件的路径
     * @param config ftp配置信息
     * @param ftpFileList 要下载的文件名集合
     * @return 
     * @see
     */
    public static File downloadZipFormFtp(String zipName,String filepath, FtpUser config,List<Map<String,Object>> ftpFileList) {
        String serverUrl = config.getIp();
        String userName = config.getUserName();
        String passWord = config.getPassword();
        byte[] buf = new byte[1024];
        FtpClient ftpClient = null;
        try {
            // 此为jdk1.7创建ftp连接
            ftpClient = FtpClient.create(serverUrl);
            // 登录
            loginToFtpServer(ftpClient, serverUrl, config.getPort(), userName, passWord);
            filepath = getFullPath(ftpClient, filepath);
            if (isDirExist(ftpClient, filepath)) {
                ftpClient.changeDirectory(filepath);
                File zipFile = new File(zipName);
                ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
                for (Map<String, Object> m : ftpFileList){
                    for (String k : m.keySet()){
                        ZipEntry entry = new ZipEntry(m.get(k).toString());
                        zipOut.putNextEntry(entry);
                        InputStream bis = getFtpClientInputStream(ftpClient,k);
                        if (bis != null) {
                            int readLen = -1;
                            while ((readLen = bis.read(buf, 0, 1024)) != -1) {
                                zipOut.write(buf, 0, readLen);
                            }
                            zipOut.closeEntry();
                            bis.close();
                        }
                    }
                }
                zipOut.close();
                log.info("ftp下载文件成功 ftp文件目录{}", filepath);
                return zipFile;
            }
        }
        catch (Exception e) {
            log.error("ftp下载文件失败", e);
        }
        finally {
            if (ftpClient != null) {
                try {
                    ftpClient.close();
                }
                catch (IOException e) {
                    log.error("", e);
                }
            }
        }
        return null;
    }
    
    /**
     * 从ftp服务器下载文件 
     * @param localFile 本地保存文件
     * @param serverPathAndFile ftp服务器上的文件 含完整路径
     * @param config ftpUserConfig
     * @see
     */
    public static void downloadFormFtp(File localFile, String serverPathAndFile, FtpUser config) {
        try {
            OutputStream out = new FileOutputStream(localFile);
            downloadFormFtp(out, serverPathAndFile, config);
        }
        catch (FileNotFoundException e) {
            log.error("", e);
        }
    }

    /**
     * 获得ftp服务器上的文件流 
     * @param serverPathAndFile ftp服务器上的文件 含完整路径
     * @param config ftpUserConfig
     * @see
     */
    public static byte[] getInputStreamFormFtp(String serverPathAndFile, FtpUser config) {
        String serverUrl = config.getIp();
        String userName = config.getUserName();
        String passWord = config.getPassword();
        String filepath = serverPathAndFile.substring(0, serverPathAndFile.lastIndexOf("/"));
        String filename = serverPathAndFile.substring(serverPathAndFile.lastIndexOf("/") + 1,
            serverPathAndFile.length());
        FtpClient ftpClient = null;
        InputStream inputStream = null;
        try {
            // 此为jdk1.7创建ftp连接
            ftpClient = FtpClient.create(serverUrl);
            // 登录
            loginToFtpServer(ftpClient, serverUrl, config.getPort(), userName, passWord);
            filepath = getFullPath(ftpClient, filepath);
            if (isDirExist(ftpClient, filepath)) {
                ftpClient.changeDirectory(filepath);
                inputStream = getFtpClientInputStream(ftpClient, filename);
            }
            return IOUtils.toByteArray(inputStream);
        }
        catch (Exception e) {
            log.debug("ftp获得文件流失败" + e.getMessage());
        }
        finally {
            IOUtils.closeQuietly(inputStream);
            if (ftpClient != null) {
                try {
                    ftpClient.close();
                }
                catch (IOException e) {
                    log.error("", e);
                }
            }
        }
        return new byte[0];
    }

    /**登录到Ftp服务器 
     * @param ftpClient
     * @param serverUrl
     * @param port
     * @param userName
     * @param passWord
     * @return 
     * @see 
     */
    private static boolean loginToFtpServer(FtpClient ftpClient, String serverUrl, String port,
                                            String userName, String passWord) {
        try {
            SocketAddress addr = new InetSocketAddress(serverUrl, Integer.valueOf(port));
            // ftpClient 先connect 再login 否则有可能耗时很久 by Awoke
            ftpClient.connect(addr);
            ftpClient.login(userName, passWord.toCharArray());
            ftpClient.setBinaryType();
            return true;
        }
        catch (IOException e) {
            log.error("Ftp 客户端打开失败");
            return false;
        }
        catch (FtpProtocolException e) {
            log.error("Ftp 客户端打开失败");
        }
        return false;
    }

    /**
     * 得到ftp客户端的输入流
     */
    private static InputStream getFtpClientInputStream(FtpClient ftpClient, String downLoadFile)
        throws IOException, FtpProtocolException {
        ftpClient.setBinaryType();
        File file = new File(downLoadFile);
        return ftpClient.getFileStream(file.getName());
    }

    /** 下载文件到本地 */
    private static void writeToLocal(InputStream inputStream, OutputStream outputStream)
        throws IOException {
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = inputStream.read(bytes, 0, bytes.length)) != -1) {
            outputStream.write(bytes, 0, len);
        }
        outputStream.flush();
    }

    /** 得到本地文件的输入流 */
    private static InputStream getLocalFileInputStream(File file)
        throws FileNotFoundException {
        return new FileInputStream(file);
    }

    /**
     * 检查文件夹在当前目录下是否存在
     * @param dir
     * @return
     */
    private static boolean isDirExist(FtpClient ftpClient, String dir) {
        String pwd = "";
        try {
            pwd = ftpClient.getWorkingDirectory();
            ftpClient.changeDirectory(dir);
            ftpClient.changeDirectory(pwd);
        }
        catch (Exception e) {
            //log.error("", e);
            return false;
        }
        return true;
    }

    /** 获得完整路径 */
    private static String getFullPath(FtpClient ftpClient, String serverPath)
        throws FtpProtocolException, IOException {
        String workingdir = ftpClient.getWorkingDirectory();
        if (!serverPath.startsWith("/")) {
            serverPath = "/" + serverPath;
        }
        if (!workingdir.equals("/")) {
            serverPath = workingdir + serverPath;
        }
        return serverPath;
    }

    /**
     * 在当前目录下创建文件夹
     * @param dir
     * @return
     * @throws Exception
     */
    private static boolean createDir(FtpClient ftpClient, String dir) {
        try {
            ftpClient.setAsciiType();
            StringTokenizer s = new StringTokenizer(dir, "/");
            s.countTokens();
            String pathName = "";
            while (s.hasMoreElements()) {
                pathName = pathName + "/" + (String)s.nextElement();
                if (!isDirExist(ftpClient, pathName)) {
                    try {
                        ftpClient.makeDirectory(pathName);
                    }
                    catch (Exception e) {
                        log.error("", e);
                        return false;
                    }
                    ftpClient.getLastResponseString();
                }
            }
            ftpClient.setBinaryType();
            return true;
        }
        catch (IOException e1) {
            log.error("", e1);
            return false;
        }
        catch (FtpProtocolException e1) {
            log.error("", e1);
        }
        return false;
    }

    /** 关闭 */
    private static void close(FtpClient ftpClient, OutputStream outputStream,
                              InputStream inputStream)
        throws IOException {
        if (inputStream != null) {
            inputStream.close();
        }
        if (outputStream != null) {
            outputStream.close();
        }
        if (ftpClient != null) {
            ftpClient.close();
        }
    }

    /**
     * Test
     * @param args 
     * @see
     */
    public static void main(String[] args) {
        String serverUrl = "192.168.0.15";
        String userName = "ftpuser";
        String passWord = "ftpuser";
        String port = "21";
        FtpUser ftpUser = new FtpUser();
        ftpUser.setUserName(userName);
        ftpUser.setPassword(passWord);
        ftpUser.setIp(serverUrl);
        ftpUser.setPort(port);
        FtpUtil.uploadToFtpServer(new File("D:\\id-card\\awoke-test.txt"), "cert/20181106",
            "awoke22.txt", ftpUser);

        /*FileOutputStream out;
        try {
            out = new FileOutputStream("D:\\id-card\\awoke-test-download.txt");
            FtpUtil.downloadFormFtp(out, "/jscl/awoke.txt", ftpUser);
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }*/
    }

}
